import { Config, ISearchModeConfig, noneOr } from "./typechecker";

export function warn(msg: string, errType?: string) {
	console.warn(
		`[Matable warn] ${msg} ${
			errType ? " [WARN::" + errType.toUpperCase() + "]" : ""
		}`
	);
}

export function error(msg: string, errType?: string) {
	console.error(
		`[Matable warn] ${msg} ${
			errType ? " [ERR::" + errType.toUpperCase() + "]" : ""
		}`
	);
}

function mergeConfigImpl(baseConf: any, mergeConfig?: any) {
	if (Object.prototype.toString.call(mergeConfig) === "[object Object]") {
		const keys = Object.keys(mergeConfig!);
		const vals = Object.values(mergeConfig!);

		for (let i = 0; i < keys.length; i++) {
			baseConf[keys[i]] = vals[i];
		}
	}
}

export type RowData = false | null | number | string | ISearchModeConfig;

export enum SearchMode {
	Hide,
	None,
	FullMatch,
	PartialMatch,
	Select,
	Sort,
	SortSelect,
	SortFullMatch,
	SortPartialMatch,
	NoneWithError,
}

export type SelectMapper = (
	key: string,
	index: number
) => {
	key: string;
	val: string;
	weight: number;
};

export class Select {
	static defaultMap: SelectMapper = (key: string, weight: number) => ({
		key,
		weight,
		val: key,
	});

	static from(arr: Array<string>, mapper = Select.defaultMap) {
		return arr.map(mapper);
	}

	static lazyRange(
		mapper?: (n: number) => string | [string, string] | [string, string, number]
	) {
		return (fromTo: [number, number]) => Select.range(fromTo, mapper);
	}

	static range(
		fromTo: [number, number],
		mapper?: (n: number) => string | [string, string] | [string, string, number]
	) {
		const [from, to] = fromTo;
		return Array(to - from)
			.fill(0)
			.map((_, i) => i + fromTo[0])
			.map((v, weight) => {
				if (!mapper) {
					return Select.defaultMap(String(v), weight);
				}
				const item = mapper(v);

				if (typeof item === "string" || typeof item === "number") {
					const key = String(item);
					return {
						key,
						weight,
						val: key,
					};
				} else if (Array(item)) {
					return {
						key: item[0],
						val: item[1] || item[0],
						weight: item[2] || weight,
					};
				}
			});
	}
}

function provideInternalMode(
	searchMode: SearchMode,
	op?: { label?: string; mode?: ISearchModeConfig["mode"] }
): Partial<ISearchModeConfig> {
	const label = noneOr(op!.label, "");
	const mode = noneOr(op!.mode, null);

	switch (searchMode) {
		case SearchMode.Hide:
			return { able: false, mode: null, label };
		case SearchMode.None:
			return { able: true, mode: null, label };
		case SearchMode.NoneWithError:
			return { able: true, mode: null, label };
		case SearchMode.FullMatch:
			return { able: true, mode: "[=]", label };
		case SearchMode.SortFullMatch:
			return { able: true, mode: "[=]", label, sort: true };
		case SearchMode.PartialMatch:
			return { able: true, mode: "[?]", label };
		case SearchMode.SortPartialMatch:
			return { able: true, mode: "[?]", label, sort: true };
		case SearchMode.Sort:
			return { able: true, mode: null, sort: true, label };

		case SearchMode.Select:
			return {
				able: true,
				label,
				mode,
			};
		case SearchMode.SortSelect:
			return {
				able: true,
				sort: true,
				label,
				mode,
			};
	}
}

function checkMode(rowValue: RowData): SearchMode {
	if (rowValue === false) {
		return SearchMode.Hide;
	} else if (rowValue === null) {
		return SearchMode.None;
	} else if (typeof rowValue === "string") {
		if (rowValue === "=" || rowValue === "==" || rowValue === "===") {
			return SearchMode.FullMatch;
		} else if (rowValue === ">=" || rowValue === "<=") {
			return SearchMode.SortFullMatch;
		} else if (rowValue === ">" || rowValue === "<") {
			return SearchMode.SortPartialMatch;
		} else {
			return SearchMode.PartialMatch;
		}
	} else if (typeof rowValue === "number") {
		return SearchMode.Sort;
	} else if (Array.isArray(rowValue)) {
		if (typeof (rowValue as any[])[0] === "string") {
			return SearchMode.Select;
		} else if (
			typeof (rowValue as any[])[0]["key"] === "string" &&
			(typeof (rowValue as any[])[0]["val"] === "string" ||
				typeof (rowValue as any[])[0]["val"] === "number") &&
			typeof (rowValue as any[])[0]["weight"] === "number"
		) {
			return SearchMode.SortSelect;
		}
	}

	return SearchMode.NoneWithError;
}

export function parseMode(
	label: string,
	mode: RowData,
	mergeConfig?: { [key: string]: string }
) {
	const searchMode = checkMode(mode);

	if (searchMode === SearchMode.NoneWithError) {
		warn(
			`Custom mode: [${mode}] for label [${label}] is invalid, the mode has been reset as <SearchMode.NoneWithError>`,
			"CHECK_MODE::WITH_ERROR"
		);
	}
	const res = provideInternalMode(searchMode, {
		label,
		mode: mode as any,
	});

	mergeConfigImpl(res, mergeConfig);

	return res;
}

export function createMode(
	row: { [key: string]: RowData },
	mergeConfig?: { [key: string]: string }
) {
	const conf = [];
	const labels = Object.keys(row);
	const values = Object.values(row);

	for (let i = 0; i < labels.length; i++) {
		conf.push(parseMode(labels[i], values[i]));
	}

	mergeConfigImpl(conf, mergeConfig);

	return conf;
}

export function createConf(
	info: [string, string] | { [key: string]: string },
	row: { [key: string]: RowData } | Array<Partial<ISearchModeConfig>>,
	mergeConfig?: { [key: string]: string }
) {
	const config = {
		index: true,
		data: info[0],
		title: info[1],
		config: Array.isArray(row) ? row : (createMode(row) as Config["config"]),
	};

	if (typeof info !== "string" && !Array.isArray(info)) {
		mergeConfigImpl(config, info);
	}

	mergeConfigImpl(config, mergeConfig);

	return config;
}
